/*****************************************************************************
Copyright 2006 Kervin L. Pierre
    EMAIL: otlkcon@adevsoft.com

This file is part of Otlkcon.

Otlkcon is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.

Otlkcon is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Lesser GNU General Public License for more details.

You should have received a copy of the Lesser GNU General Public License
along with Otlkcon; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*******************************************************************************/

#include "StdAfx.h"
#include "O_IStream.h"

O_IStream::O_IStream()
{
    cRef = 0;
    
    memory         = NULL;
    memory_pointer = NULL;
    memory_size         = 0;
    memory_bytes_remain = 0;
    buffer_capacity     = 0;
	nTransactionID		= 0;

    InitializeCriticalSection(&objectCS);
}

O_IStream::~O_IStream()
{
    Destroy();
}

HRESULT O_IStream::Create(char *df, int pt, int fl, char *tn, int TransactionID,
                          LPALLOCATEBUFFER lb, LPALLOCATEMORE lm, LPFREEBUFFER lf)
{
    HRESULT result = S_OK;
    int sqlRes;
    int rowCount;

    lpAllocateBuffer = lb;
    lpAllocateMore   = lm;
    lpFreeBuffer     = lf;
	
    flags = fl;
	nTransactionID = TransactionID;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::Create");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IStream::Create");

    memObj = new otlkcon_memory(lb, lm, lf);

    if( PROP_TYPE(pt) != PT_OBJECT 
        && PROP_TYPE(pt) != PT_STRING8
		&& PROP_TYPE(pt) != PT_UNICODE
		&& PROP_TYPE(pt) != PT_BINARY)
    {
        //For now we only open on PT_OBJECTs and PT_STRING8
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"IStream::Create() : Invalid propTag: 0x%0.8X\n", pt);
        
        result = E_FAIL;
        goto create_end;
    }

    memory = (LPBYTE)memObj->malloc( OTLKCON_ISTREAM_MEMORY_SIZE );
    buffer_capacity = OTLKCON_ISTREAM_MEMORY_SIZE;

    tableName = memObj->_strdup(tn);
    propTag   = pt;
    memory_pointer  = memory;
    memory_bytes_remain = buffer_capacity;

    databaseFilename = _strdup(df);

    sqlObj = GetSqlObj();
    sqlObj->init("O_IStream::Create", databaseFilename);

    //Does the table exist?
    sqlRes = sqlObj->sqlCmdSingleInt( &rowCount, 0,
        "SELECT COUNT(*) FROM sqlite_master WHERE ( type='table' ) AND\
        (name='%s');", tableName);

    if ( rowCount == 0 )
    {
        result = MAPI_E_INVALID_PARAMETER;
        goto create_end;
    }

    cRef = 1;

    // WARNING: A Revert() is needed to read the property from disk

    //Test if needed. Property is corrupted during testing
    //result = TestDriver();

create_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IStream::Create");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::Create");
    
    return result;
}

void O_IStream::Destroy()
{
    ;
}

STDMETHODIMP_(otlkcon_sqlite_helper *) O_IStream::GetSqlObj()
{
    HRESULT result = S_OK;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::GetSqlObj");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IStream::GetSqlObj");

	otlkcon_sqlite_helper * sqlObj = 
		(otlkcon_sqlite_helper *)TlsGetValue(otlkcon_tlsIndex);
    if( sqlObj == NULL )
    {
        sqlObj = new otlkcon_sqlite_helper( databaseFilename, memObj );
		if(NULL != sqlObj)
		{
			if(!TlsSetValue( otlkcon_tlsIndex, sqlObj )) 
			{
				OTLKCON_DEBUGBREAK;
				delete sqlObj;
				sqlObj = NULL;
			} // if
		} // if
		else
		{
			OTLKCON_DEBUGBREAK
		} // else
    }
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IStream::GetSqlObj");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::GetSqlObj");

    return sqlObj;
}

STDMETHODIMP O_IStream::QueryInterface( 
                REFIID riid,
                void **ppv)
{
    HRESULT result = S_OK;

    OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, L"inside O_IFolder::QueryInterface. \
 {%0.8X-%0.4X-%0.4X-%0.2X%0.2X-%0.2X%0.2X%0.2X%0.2X%0.2X%0.2X}\n",
        riid.Data1, riid.Data2, riid.Data3, riid.Data4[0], riid.Data4[1],
        riid.Data4[2], riid.Data4[3], riid.Data4[4], riid.Data4[5],
        riid.Data4[6], riid.Data4[7]);

    *ppv = NULL;

    if( memcmp(&riid, &IID_IUnknown, sizeof(IID)) &&
        memcmp(&riid, &IID_ISequentialStream, sizeof(IID)) &&
        memcmp(&riid, &IID_IStream, sizeof(IID)))
    { 
        result = ResultFromScode(E_NOINTERFACE);
        return result;
    }

    AddRef();
    *ppv = (LPVOID)this;

    return result;
}
        
ULONG STDMETHODCALLTYPE O_IStream::AddRef()
{
    return ++cRef;
}
        
ULONG STDMETHODCALLTYPE O_IStream::Release()
{
    if ( --cRef == 0 )
    {
        Destroy();
    }

    return cRef;
}

STDMETHODIMP O_IStream::Read( void *pv, ULONG cb, ULONG *pcbRead )
{
    HRESULT result = S_OK;
    ULONGLONG numBytes = 0;

    numBytes = memory_bytes_remain>cb?cb:memory_bytes_remain;
    if( numBytes > 0 )
    {
        if( numBytes >= ULONG_MAX )
        {
            return E_FAIL;
        }
        memcpy(pv, memory_pointer, (size_t)numBytes);
        memory_bytes_remain -= numBytes;
        if( memory_bytes_remain < 0 )
            memory_bytes_remain = 0;
        memory_pointer += numBytes;
    }

    if( numBytes >= ULONG_MAX )
    {
        return E_FAIL;
    }

    if( pcbRead )
        *pcbRead = (ULONG)numBytes;

    return result;
}
    
STDMETHODIMP O_IStream::Write( const void *pv, ULONG cb, ULONG *pcbWritten )
{
    HRESULT result = S_OK;
    ULONGLONG numBytes = 0;

    if( (memory_pointer+cb) > ( memory+buffer_capacity-1) )
    {
        result = STG_E_MEDIUMFULL;
    }

    ULONGLONG excess_capacity 
        = memory_bytes_remain + (buffer_capacity - memory_size);

    numBytes = excess_capacity>cb?cb:excess_capacity;
    if( numBytes > 0 )
    {
        memcpy(memory_pointer, pv, (size_t)numBytes);
        if(memory_bytes_remain-numBytes<0)
        {
            //memory size grew
            memory_size += ( numBytes - memory_bytes_remain );
            memory_bytes_remain = 0;
        }
        else 
        {
            memory_bytes_remain -= numBytes;
			memory_size += numBytes;
        }
            
        memory_pointer += numBytes;
    }

    if( numBytes > ULONG_MAX )
    {
        OTLKCON_DEBUGBREAK;
        return E_FAIL;
    }

    if( pcbWritten )
        *pcbWritten = (ULONG)numBytes;

    return result;
}

STDMETHODIMP O_IStream::Seek( LARGE_INTEGER dlibMove, DWORD dwOrigin,
        ULARGE_INTEGER *plibNewPosition)
{
    HRESULT result = S_OK;
    LPBYTE tempPtr;
    ULONGLONG tempBytesRemain = memory_bytes_remain;


    switch( dwOrigin )
    {
        case STREAM_SEEK_SET:
            tempPtr = memory+dlibMove.QuadPart;
            tempBytesRemain = memory_size - dlibMove.QuadPart;
            break;

        case STREAM_SEEK_CUR:
            tempPtr = memory_pointer+dlibMove.QuadPart;
            tempBytesRemain -= dlibMove.QuadPart;
            break;

        case STREAM_SEEK_END:
            tempPtr = memory+memory_size+dlibMove.QuadPart;
            tempBytesRemain = -dlibMove.QuadPart;
            break;

        default:
            break;
    }

    if( tempPtr < memory || tempPtr > (memory+memory_size) )
        result = STG_E_INVALIDPOINTER;
    else
    {
        memory_pointer = tempPtr;
        memory_bytes_remain = tempBytesRemain;
    }
	
	if(NULL != plibNewPosition)
	{
		plibNewPosition->QuadPart = memory_size - memory_bytes_remain;
	} // if

    return result;
}

STDMETHODIMP O_IStream::SetSize(ULARGE_INTEGER libNewSize)
{
    HRESULT result = S_OK;

    if( libNewSize.QuadPart > memory_size )
        result = STG_E_MEDIUMFULL;

    return result;
}

STDMETHODIMP O_IStream::CopyTo( 
        IStream *pstm,
        ULARGE_INTEGER cb,
        ULARGE_INTEGER *pcbRead,
        ULARGE_INTEGER *pcbWritten)
{
    HRESULT result = STG_E_INVALIDFUNCTION;

    return result;
}

STDMETHODIMP O_IStream::Commit( DWORD grfCommitFlags )
{
    HRESULT result = S_OK;
    int sqlRes     = 0;
    sqlite3_stmt * queryStmt;
    char * queryStr;
    char * errMsg = NULL;
    const char **queryRemainder = NULL;
    int isMV=0, j=0;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::Commit");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IStream::Commit");

    sqlObj = GetSqlObj();

    sqlRes = sqlObj->buildQuery( &queryStr,
		"INSERT OR REPLACE INTO %s VALUES (%d, %d, %d, %d, %d, :DAT )",
		tableName, (int)(5*sizeof(ULONG)+memory_size), propTag, 0, nTransactionID, 0 );

    if( memory_size > INT_MAX )
    {
        OTLKCON_DEBUGBREAK;
        result = E_FAIL;
        goto commit_end;
    }

    sqlRes = sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                        &queryStmt, queryRemainder);
	if(SQLITE_OK == sqlRes)
	{
		switch(PROP_TYPE(propTag))
		{
		case PT_BINARY:
			sqlRes = sqlite3_bind_blob(queryStmt, 1, (LPVOID)memory, (int)memory_size, 
				SQLITE_TRANSIENT);
			break;
		case PT_UNICODE:
			sqlRes = sqlite3_bind_text16(queryStmt, 1, (LPVOID)memory, (int)memory_size,
				SQLITE_TRANSIENT);
			break;
		case PT_STRING8:
			sqlRes = sqlite3_bind_text(queryStmt, 1, (LPSTR)memory, (int)memory_size,
				SQLITE_TRANSIENT);
			break;
		default:
			result = E_FAIL;
		} // switch

		if(SQLITE_OK == sqlRes) 
		{
			sqlRes = sqlite3_step(queryStmt);  
			if(SQLITE_DONE != sqlRes)
			{
				OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
					L"IStream::Commit sqlite3_step error: %S", 
					sqlite3_errmsg(sqlObj->databaseHandle));
				result = E_FAIL;
			} // if
		} // if
		else
		{
			OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
				L"IStream::Commit sqlite3_bind_blob error: %S", 
				sqlite3_errmsg(sqlObj->databaseHandle));
			result = E_FAIL;
		} // else
		sqlRes = sqlite3_finalize(queryStmt);
		if ( sqlRes != SQLITE_OK )
		{
			OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
				L"IStream::Commit sqlite3_finalize error: %S", 
				sqlite3_errmsg(sqlObj->databaseHandle));
			result = E_FAIL;
		}
	} // if
	else
	{
		OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
			L"IStream::Commit sqlite3_prepare error: %S", 
			sqlite3_errmsg(sqlObj->databaseHandle));
		result = E_FAIL;
	} // else

    memObj->free(queryStr);

commit_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IStream::Commit");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::Commit");

    return result;
}

// select 'propTag' from 'tableName' into 'memory'
STDMETHODIMP O_IStream::Revert()
{
    HRESULT result = S_OK;
    sqlite3_stmt * queryStmt;
    char * queryStr;
    const char **queryRemainder = NULL;
    int sqlRes;
    ULONGLONG tmpBinSize;
    int rowCount;
    void *tmpBin;

    OTLKCON_ENTERCRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::Revert");
    OTLKCON_ENTERCRITICALSECTION(&objectCS, "OBJ> O_IStream::Revert");

    sqlObj = GetSqlObj();

    sqlRes = sqlObj->sqlCmdSingleInt( &rowCount, 0,
                "SELECT COUNT(*) FROM %s WHERE propTag = %d", 
                tableName, propTag );

    if( rowCount == 0 )
    {
        //PropTag not found
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO,  OTLKCON_LOG_TARGET_ALL, 
            L"IStream::Revert() : Property not found: 0x%0.8X\n", propTag);
        result = E_FAIL;
        goto revert_end;
    }

    //Get the rows themselves
    sqlRes = sqlObj->buildQuery( &queryStr,
                    "SELECT * FROM %s WHERE propTag = %d;", tableName, propTag );

    if( rowCount > 1 )
    {
        //Multi-value property or very large property
        //TODO: Handle those.
        ;
    }

    sqlite3_prepare( sqlObj->databaseHandle, queryStr, -1, 
                            &queryStmt, queryRemainder);
    sqlite3_step(queryStmt);    
    tmpBinSize = sqlite3_column_bytes(queryStmt, 
                                    OTLKCON_IPROP_TABLE_DATA_COL);
    if( tmpBinSize > buffer_capacity )
    {
        OTLKCON_DEBUGBREAK;
        result = E_FAIL;
        goto revert_end;
    }

    memset(memory, 0, (size_t)buffer_capacity);
    memory_size = tmpBinSize;
    memory_bytes_remain = buffer_capacity - memory_size;
    memory_pointer = memory;
    
    if( tmpBinSize > 0 )
    {
        if( tmpBinSize >= ULONG_MAX-1 )
        {
            OTLKCON_DEBUGBREAK;
            result = E_FAIL;
            goto revert_end;
        }
        tmpBin = memObj->malloc( (ULONG)tmpBinSize+1 );
        // FIXME: Can this be right??
        tmpBin = (void *)sqlite3_column_blob(queryStmt, 
                                OTLKCON_IPROP_TABLE_DATA_COL);
        memcpy(memory, tmpBin, (size_t)tmpBinSize);
    }

    sqlRes = sqlite3_finalize(queryStmt);

    if ( sqlRes != SQLITE_OK )
    {
        OTLKCON_LOG_PRINTF(OTLKCON_LOG_LEVEL_INFO, OTLKCON_LOG_TARGET_ALL, 
            L"ITable::Load sqlite3_finalize error: %S", 
            sqlite3_errmsg(sqlObj->databaseHandle));
        result = E_FAIL;
    }

    memObj->free(queryStr);

revert_end:
    OTLKCON_LEAVECRITICALSECTION(&objectCS, "OBJ> O_IStream::Revert");
    OTLKCON_LEAVECRITICALSECTION(&otlkcon_sqlite_cs, "SQL> O_IStream::Revert");
    return result;
}

STDMETHODIMP O_IStream::LockRegion( 
        ULARGE_INTEGER libOffset,
        ULARGE_INTEGER cb,
        DWORD dwLockType)
{
    HRESULT result = STG_E_INVALIDFUNCTION;

    return result;
}

STDMETHODIMP O_IStream::UnlockRegion( 
        ULARGE_INTEGER libOffset,
        ULARGE_INTEGER cb,
        DWORD dwLockType)
{
    HRESULT result = STG_E_INVALIDFUNCTION;

    return result;
}

STDMETHODIMP O_IStream::Stat( STATSTG *pstatstg, DWORD grfStatFlag )
{
    HRESULT result = S_OK;

    memset(pstatstg, 0, sizeof(STATSTG));

    pstatstg->type = STGTY_STREAM;
    pstatstg->cbSize.QuadPart = memory_size;

    return result;
}

STDMETHODIMP O_IStream::Clone( IStream **ppstm )
{
    HRESULT result = S_OK;

    return result;
}

HRESULT O_IStream::TestDriver()
{
    HRESULT result = S_OK;
    LPBYTE mem = NULL;
    ULONG noBytes = 0;
    STATSTG stat = {0};
    ULARGE_INTEGER pos;
    LARGE_INTEGER moves = {0};

    result = Revert();

    lpAllocateBuffer(128, (LPVOID *)&mem);
    memset(mem, 11, 32);
    result = Write(mem, 32, &noBytes);
    result = Commit(0);

    memset(mem, 12, 32);
    result = Read(mem, 10, &noBytes);
    result = Read(mem+10, 10, &noBytes);
    result = Stat(&stat, 0);

    _snprintf((char *)mem, 32, "Corrupted Property!!!!");

    result = Write(mem, 12, &noBytes);
    result = Commit(0);
    result = Revert();

    memset(mem, 12, 32);
    result = Seek(moves,STREAM_SEEK_SET,&pos);
    result = Read(mem, 32, &noBytes);
    result = Read(mem, 32, &noBytes);
    result = Stat(&stat, 0);

    return result;
}